﻿using System.Text;

namespace FolderConverter.NetConsole
{
    public class FileWrapper
    {

        public IEnumerable<string> IgnorePaths => _ignorePaths;

        private List<string> _ignorePaths = new List<string>();

        public void CleanIgnorePaths()
        {
            _ignorePaths.Clear();
        }

        public void AddIgnorePaths(string[] ignoreDirectories)
        {
            foreach (string dir in ignoreDirectories)
            {
                if (!Path.Exists(dir))
                {
                    throw new ArgumentException($"Invalid path {dir}.");
                }

                var path = Path.GetFullPath(dir);

                // 去重
                if (!_ignorePaths.Contains(path))
                {
                    _ignorePaths.Add(path);
                }
            }
        }

        public void Pack(string directory, string outFileName)
        {
            Console.WriteLine("开始打包数据...");
            if (!Directory.Exists(directory))
            {
                throw new DirectoryNotFoundException(directory);
            }

            var directoryInfo = new DirectoryInfo(directory);

            var fileInfso = GetDirectoryFileInfos(directoryInfo.FullName);

            var wrapperItems = PackFileInfos(directoryInfo.FullName, outFileName, fileInfso);
        }

        public void Unpack(string outDirectory, string inputFileName)
        {
            Console.WriteLine("开始解包数据...");
            if (!Directory.Exists(outDirectory))
            {
                throw new DirectoryNotFoundException(outDirectory);
            }

            if (!File.Exists(inputFileName))
            {
                throw new FileNotFoundException(inputFileName);
            }

            using var fileStream = File.Open(inputFileName, FileMode.Open);
            using var reader = new StreamReader(fileStream);
            var firstLine = reader.ReadLine();

            if (string.IsNullOrWhiteSpace(firstLine))
            {
                throw new InvalidOperationException("文件格式错误!");
            }

            var offsetString = Encryptor.ConvertFromEncryptedBase64String(firstLine);
            var contentSize = int.Parse(offsetString);
            // 第一行的长度，需要加一个换行符 \n 长度
            var firstLineSize = Encoding.UTF8.GetByteCount(firstLine) + 1;
            // 头部内容最后一行也是一个换行符，所以需要跳过换行符
            var headerSize = contentSize + firstLineSize + 1;

            if (string.IsNullOrEmpty(offsetString))
            {
                throw new InvalidOperationException("文件格式错误!");
            }

            var wrapperImtes = ParseWrapperInfo(fileStream, firstLineSize, contentSize);
            var base64FileInfos = ParseBase64FileInfo(outDirectory, fileStream, headerSize, wrapperImtes);
            OutBase64FileInfoToFile(base64FileInfos);
        }

        void OutBase64FileInfoToFile(List<Base64FileInfo> base64FileInfos)
        {
            foreach (var base64FileInfo in base64FileInfos)
            {
                try
                {
                    var fileInfo = Base64FileConverter.FromBase64FileInfo(base64FileInfo);
                    CreateFile(fileInfo);
                }
                catch (Exception ex)
                {
                    Console.WriteLine("解析失败：" + base64FileInfo.Path);
                }
            }
        }

        void CreateFile(FileInfo fileInfo)
        {
            var directory = Path.GetDirectoryName(fileInfo.Path);

            if (directory == null)
            {
                throw new InvalidOperationException("无效的文件路径！");
            }

            // 目录不存在，先创建目录
            if (!Directory.Exists(directory))
            {
                Directory.CreateDirectory(directory);
            }

            FileStream fileStream;

            if (!File.Exists(fileInfo.Path))
            {
                fileStream = File.Create(fileInfo.Path, fileInfo.Content.Length);
            }
            else
            {
                fileStream = File.OpenWrite(fileInfo.Path);
            }

            Console.WriteLine($"输出文件：{fileInfo.Path}");

            fileStream.Position = 0;
            fileStream.Write(fileInfo.Content, 0, fileInfo.Content.Length);
            fileStream.Flush();
            fileStream.Close();
        }

        private List<Base64FileInfo> ParseBase64FileInfo(
            string outDirectory,
            FileStream fileStream,
            int contentOffset,
            List<FileWrapperItem> fileWrapperItems)
        {
            var bytes = new byte[fileStream.Length - contentOffset];
            fileStream.Seek(contentOffset, SeekOrigin.Begin);
            var size = fileStream.Read(bytes, 0, bytes.Length);

            if (size != bytes.Length)
            {
                throw new InvalidOperationException("文件格式错误！");
            }

            var offset = 0;
            var infos = new List<Base64FileInfo>();

            foreach (var fileWrapperItem in fileWrapperItems)
            {
                var fullPath = Path.Combine(outDirectory, fileWrapperItem.RelativePath);
                Console.WriteLine($"Base64编码文件包拆分：{fileWrapperItem.Length}\t{fullPath}");
                // 取出文件的字节内容
                var fileBytes = bytes.Skip(offset).Take(fileWrapperItem.Length).ToArray();
                offset += fileWrapperItem.Length;
                // 转换成字符串，不过这个字符串是 base64 格式的
                var base64String = Encoding.UTF8.GetString(fileBytes, 0, fileBytes.Length);
                infos.Add(new Base64FileInfo(fullPath, base64String));
            }

            return infos;
        }

        private List<FileWrapperItem> ParseWrapperInfo(FileStream fileStream, int contentOffset, int contentSize)
        {
            var items = new List<FileWrapperItem>();
            var buffer = new byte[contentSize];
            fileStream.Seek(contentOffset + 1, SeekOrigin.Begin);
            var size = fileStream.Read(buffer, 0, contentSize);

            if (size != contentSize)
            {
                throw new InvalidOperationException("文件格式错误！");
            }

            var stringValue = Encoding.UTF8.GetString(buffer);
            StringReader stringReader = new StringReader(stringValue);


            do
            {
                var line = stringReader.ReadLine();
                if (string.IsNullOrWhiteSpace(line))
                {
                    break;
                }
                var lineString = Encryptor.ConvertFromEncryptedBase64String(line);
                var values = lineString.Split(":");
                Console.WriteLine($"解析打包信息：{values[0]}\t{values[1]}");
                items.Add(new FileWrapperItem(values[0], int.Parse(values[1])));
            } while (true);

            return items;
        }

        private List<Base64FileInfo> GetDirectoryFileInfos(string directory)
        {
            List<Base64FileInfo> fileInfos = new List<Base64FileInfo>();
            DirectoryInfo currentDirectory = new DirectoryInfo(directory);

            Stack<DirectoryInfo> stack = new Stack<DirectoryInfo>();
            stack.Push(currentDirectory);

            while (stack.Any())
            {
                currentDirectory = stack.Pop();

                // 打包当前目录的所有文件
                AddFileInfos(currentDirectory, fileInfos);

                var childDirs = currentDirectory.GetDirectories();

                foreach (var childDir in childDirs)
                {
                    // 这里过滤掉不需要包含的目录
                    if (!_ignorePaths.Contains(childDir.FullName))
                    {
                        stack.Push(childDir);
                    }
                }
            }

            return fileInfos;
        }

        private void AddFileInfos(DirectoryInfo directoryInfo, List<Base64FileInfo> fileInfos)
        {
            var files = directoryInfo.GetFiles();

            foreach (var file in files)
            {
                Console.WriteLine($"转换Base64编码格式：{file.FullName} ");
                var info = Base64FileConverter.ConvertToBase64FileInfo(file.FullName);
                fileInfos.Add(info);
            }
        }

        private List<FileWrapperItem> PackFileInfos(string baseDirectory, string path, List<Base64FileInfo> fileInfos)
        {
            FileStream fileStream = File.Exists(path) ? File.Open(path, FileMode.Create, FileAccess.Write) : File.Create(path);

            var size = 0;
            var wrapperItems = new List<FileWrapperItem>();
            using var buffer = new MemoryStream();

            foreach (var file in fileInfos)
            {
                if (_ignorePaths.Contains(file.Path))
                {
                    Console.WriteLine($"忽略文件数据：{file.Path}");
                    continue;
                }

                Console.WriteLine($"写入打包数据：{file.Path}");

                // 获取 base64 字符串的流
                var bytes = Encoding.UTF8.GetBytes(file.Content);
                buffer.Write(bytes, 0, bytes.Length);
                size += bytes.Length;
                var relativePath = file.Path.Remove(0, baseDirectory.Length + 1);
                wrapperItems.Add(new FileWrapperItem(relativePath, bytes.Length));
            }

            var content = MakeWrapperFileContent(wrapperItems);
            // 写入文件头
            fileStream.Write(Encoding.UTF8.GetBytes(content));
            // 写入文件内容
            buffer.WriteTo(fileStream);

            fileStream.Flush();
            fileStream.Close();

            return wrapperItems;
        }

        private string MakeWrapperFileContent(List<FileWrapperItem> items)
        {
            StringBuilder stringBuilder = new StringBuilder();
            foreach (var item in items)
            {
                Console.WriteLine($"生成打包信息：{item.RelativePath}:{item.Length}");
                var value = Encryptor.ConvertToEncryptedBase64String(item.RelativePath + ":" + item.Length);
                stringBuilder.AppendLine(value);
            }

            // 插入一行，用来索引内容的长度
            stringBuilder.Insert(0, Encryptor.ConvertToEncryptedBase64String(stringBuilder.Length.ToString()) + "\n");
            return stringBuilder.ToString();
        }
    }
}
